/*
 * weixin等登录时使用的ResponseClient
 * @Author: sean_kei@163.com
 * @Date: 2021-11-23 10:39:12
 * @LastEditors: sean_kei@163.com
 * @LastEditTime: 2021-11-23 14:59:32
 */
package com.wxhandle.cleandemo.sso.oauth2.client;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import com.wxhandle.cleandemo.sso.oauth2.converter.CommonOAuth2AccessTokenResponseHttpMessageConverter;

import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequestEntityConverter;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

public class CleanOAuth2AccessTokenResponseClient implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
    
    private final DefaultAuthorizationCodeTokenResponseClient authorizationCodeTokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
    private final HttpMessageConverter<MultiValueMap<String, ?>> formHttpMessageConverter = new FormHttpMessageConverter();
    private final RestTemplate restTemplate = new RestTemplate();
    
    private final OAuth2AccessTokenResponseHttpMessageConverter oAuth2AccessTokenResponseHttpMessageConverter = new CommonOAuth2AccessTokenResponseHttpMessageConverter();

    public CleanOAuth2AccessTokenResponseClient() {
        List<MediaType> mediaTypes = new ArrayList<>();
        mediaTypes.addAll(oAuth2AccessTokenResponseHttpMessageConverter.getSupportedMediaTypes());
        mediaTypes.addAll(Arrays.asList(new MediaType("text", "html", Collections.singletonMap("charset", "utf-8")), MediaType.TEXT_PLAIN));
        oAuth2AccessTokenResponseHttpMessageConverter.setSupportedMediaTypes(mediaTypes);

        restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
        authorizationCodeTokenResponseClient.setRestOperations(restTemplate);
        authorizationCodeTokenResponseClient.setRequestEntityConverter(new CleanOAuth2AuthorizationCodeGrantRequestEntityConverter());
    }

    @Override
    public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) {
        restTemplate.setMessageConverters(Arrays.asList(formHttpMessageConverter, oAuth2AccessTokenResponseHttpMessageConverter));

        return authorizationCodeTokenResponseClient.getTokenResponse(authorizationGrantRequest);
    }
    
    /**
     * 自定义token request转换器{@link OAuth2AuthorizationCodeGrantRequest}
     *
     * @author Sai
     * @since 1.0.5
     */
    private static final class CleanOAuth2AuthorizationCodeGrantRequestEntityConverter extends OAuth2AuthorizationCodeGrantRequestEntityConverter {
        @SuppressWarnings("ConstantConditions")
        @Override
        public RequestEntity<?> convert(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
            RequestEntity<?> request = super.convert(authorizationCodeGrantRequest);
            if (Arrays.asList("weixin", "dgut", "qq").contains(authorizationCodeGrantRequest.getClientRegistration().getRegistrationId()))
                return new RequestEntity<>(reBulidFormParameters(request, authorizationCodeGrantRequest), request.getHeaders(), request.getMethod(), request.getUrl());
            return request;
        }

        /**
         * 根据QQ、weixin、Dgut的token接口要求，重构请求参数
         *
         * @param request                       {@link RequestEntity}
         * @param authorizationCodeGrantRequest {@link OAuth2AuthorizationCodeGrantRequest}
         * @return 新请求参数
         */
        @SuppressWarnings("unchecked")
        private MultiValueMap<String, String> reBulidFormParameters(RequestEntity<?> request, OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
            MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
            formParameters.addAll((MultiValueMap<String, String>) Objects.requireNonNull(Objects.requireNonNull(request).getBody()));

            switch (authorizationCodeGrantRequest.getClientRegistration().getRegistrationId()) {
                case "dgut":
                    formParameters.add("token", authorizationCodeGrantRequest.getAuthorizationExchange().getAuthorizationResponse().getCode());
                    formParameters.add("appid", authorizationCodeGrantRequest.getClientRegistration().getClientId());
                    formParameters.add("appsecret", authorizationCodeGrantRequest.getClientRegistration().getClientSecret());
                    formParameters.add("userip", (String) authorizationCodeGrantRequest.getAuthorizationExchange().getAuthorizationRequest().getAdditionalParameters().get("ip"));
                    break;
                case "weixin":
                    // 微信的token接口使用的参数名为appid和secret，不是oauth2标准中的client_id，这里不作修改，直接增加参数。
                    formParameters.add("appid", authorizationCodeGrantRequest.getClientRegistration().getClientId());
                    formParameters.add("secret", authorizationCodeGrantRequest.getClientRegistration().getClientSecret());
                    break;
                case "qq":
                    break;
            }

            return formParameters;
        }
    }
}
